﻿using System;
using System.Text;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Collections;
using System.Diagnostics;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Audio;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.GamerServices;
using Microsoft.Xna.Framework.Graphics;
using Microsoft.Xna.Framework.Input;
using Microsoft.Xna.Framework.Media;

namespace FUSE
{
    class MapValidator
    {
        //0 is unchecked
        //1 is checked
        //2 voronoi
        //Matrix matrixDiagram;

        private int[,] voronoiDiagram;

        private Hashtable threads = new Hashtable();
        private Hashtable labelOrigins = new Hashtable();
        private Hashtable maxReachRadius = new Hashtable();

        private Boolean mapIsValid = false;

        private int counter;

        private List<Vector2> pending = new List<Vector2>();

        private List<Vector2> solutionQueue = new List<Vector2>();

        private Map galaxyMap;

        public MapValidator(Map map)
        {
            this.galaxyMap = map;

            voronoiDiagram = new int[map.galaxyForces.GetLength(0), map.galaxyForces.GetLength(1)];
            voronoiDiagram.Initialize();
        }

        private void outputMatrixToFile()
        {
            FileInfo t = new FileInfo("MatrixVoronoi"+ ++counter +".txt");
            StreamWriter Tex = t.CreateText();
            StringBuilder sTemp = new StringBuilder();

            for (int y = 0; y < voronoiDiagram.GetLength(1); y++)
            {
                for (int x = 0; x < voronoiDiagram.GetLength(0); x++)
                {
                    sTemp.Append(voronoiDiagram[x, y] + "");
                }
                Tex.WriteLine(sTemp);
                sTemp.Clear();
            }

              
            Tex.Write(Tex.NewLine);
            Tex.Close();
            Console.WriteLine(voronoiDiagram.GetLength(0) + " x " + voronoiDiagram.GetLength(1) + " Matrix outputed ");
        }

        public Boolean Validate()
        {
            mapIsValid = false;

            factorGalaxyComponents();
            
            /*for (int i = 0; i < 4; i++)
            {
                Thread thread = new Thread(new ThreadStart(factorNeighbours));
                thread.Name = "MapValidatorThread" + i;
                //thread.Priority = ThreadPriority.AboveNormal;
                threads.Add(i, thread);
            }

            for (int i = 0; i < threads.Count; i++)
            {
                ((Thread)threads[i]).Start();
            }

            IEnumerator Enum = threads.Keys.GetEnumerator();
            Boolean hasNext = Enum.MoveNext();

            while (hasNext)
            {
                if (((Thread)threads[Enum.Current]).ThreadState == System.Threading.ThreadState.Stopped
                    && pending.Count == 0)
                {
                    threads.Remove(Enum.Current);
                    Enum = threads.Keys.GetEnumerator();
                    hasNext = Enum.MoveNext();
                }
                else if (((Thread)threads[Enum.Current]).ThreadState == System.Threading.ThreadState.WaitSleepJoin
                    && pending.Count == 0)
                {
                    threads.Remove(Enum.Current);
                    Enum = threads.Keys.GetEnumerator();
                    hasNext = Enum.MoveNext();
                }
                else
                {
                    hasNext = Enum.MoveNext();
                    if (!hasNext)
                    {
                        Enum = threads.Keys.GetEnumerator();
                        hasNext = Enum.MoveNext();
                    }
                }
                
            }*/

            factorNeighbours();

            //outputMatrixToFile();

            findSolutions();

            //outputMatrixToFile();

            return mapIsValid;
        }

        private void factorGalaxyComponents()
        {
            int label = 1;
            foreach (GalaxyComponent item in this.galaxyMap.galaxyComponentsTable.Values)
            {
                if (!(item is StartPlanet) && !(item is EndPlanet))
                {
                    double radius = item.ComponentBounds.radius;
                    //double radianDegree = (2 * Math.PI) / (4 * radius);

                    voronoiDiagram[(int)item.ComponentBounds.centerPOS.X, (int)item.ComponentBounds.centerPOS.Y] = label;
                    if (item is MassedComponent)
                        maxReachRadius.Add(label, ((MassedComponent)item).SlingCircle.radius);
                    else
                        maxReachRadius.Add(label, item.ComponentBounds.radius);

                    labelOrigins.Add(label, item.ComponentBounds.centerPOS);

                    for (int x = (int)Round(item.ComponentBounds.centerPOS.X - item.ComponentBounds.radius); x <= (int)Round(item.ComponentBounds.centerPOS.X + item.ComponentBounds.radius); x++)
                    {
                        for (int y = (int)Round(item.ComponentBounds.centerPOS.Y - item.ComponentBounds.radius); y <= (int)Round(item.ComponentBounds.centerPOS.Y + item.ComponentBounds.radius); y++)
                        {
                            if (Round(Vector2.Distance(item.ComponentBounds.centerPOS, new Vector2(x, y))) < Round(item.ComponentBounds.radius))
                            {
                                voronoiDiagram[x, y] = label;
                            }
                            else if (Round(Vector2.Distance(item.ComponentBounds.centerPOS, new Vector2(x, y))) == Round(item.ComponentBounds.radius))
                            {
                                voronoiDiagram[x, y] = label;
                                pending.Add(new Vector2(x, y));
                            }
                        }
                    }

                    label++;
                }
            }
        }

        private void factorNeighbours()
        {
            Vector2 sourcePoint;
            
            //main loop
            while (pending.Count > 0)
            {

                sourcePoint = pending.First();
                pending.Remove(sourcePoint);

                int currLabel = voronoiDiagram[(int)sourcePoint.X, (int)sourcePoint.Y];

                GenericForce force = galaxyMap.galaxyForces[(int)sourcePoint.X, (int)sourcePoint.Y];
                double calcforce = Math.Sqrt(Math.Pow(force.Force.X, 2) + Math.Pow(force.Force.Y, 2));
                double roundedForce = Round(calcforce);
                
                if ((currLabel != -1) && (currLabel != 0))
                {
                    double roundedMaxReachRadius = Round((float)maxReachRadius[currLabel]);
                    int maxIncrement = Math.Max((int)roundedForce, 1);
                    //int maxIncrement = (int)roundedForce;
                    
                    //iterate up and down the neighbours surrounding currCheck
                    for (int y = ((int)sourcePoint.Y - maxIncrement); y <= ((int)sourcePoint.Y + maxIncrement); y++)
                    {
                        //iterate left and right through the neighbours surrounding currCheck
                        for (int x = ((int)sourcePoint.X - maxIncrement); x <= ((int)sourcePoint.X + maxIncrement); x++)
                        {
                            //if its neighbour is within the map bounds
                            if (x >= 0 && y >= 0 && x < this.galaxyMap.galaxyForces.GetLength(0)
                                            && y < this.galaxyMap.galaxyForces.GetLength(1))
                            {
                                Vector2 currNeighbour = new Vector2(x, y);
                                int currNeighbourLabel = voronoiDiagram[x, y];

                                double distanceToSourcePoint = Vector2.Distance(sourcePoint, currNeighbour);
                                double distanceToOrigin = Vector2.Distance((Vector2)labelOrigins[currLabel], currNeighbour);

                                double roundedDistanceToOrigin = Round(distanceToOrigin);
                                double roundedDistanceToSourcePoint = Round(distanceToSourcePoint);


                                //0 = unchecked
                                //Not 0 = checked
                                //if current label != label of neighbour ==> found voronoi path
                                //-1 = Voronoi

                                //Found unchecked neighbour
                                if ((roundedDistanceToSourcePoint < maxIncrement)/* && (roundedDistanceToOrigin < roundedMaxReachRadius)*/)
                                {
                                    if ((currNeighbourLabel == 0))
                                    {
                                        voronoiDiagram[x, y] = currLabel;
                                    }
                                    //Found another search label ==> Voronoi
                                    else if (currNeighbourLabel != currLabel)
                                    {
                                        voronoiDiagram[x, y] = -1;
                                    }
                                }
                                else if (roundedDistanceToOrigin <= roundedMaxReachRadius)
                                {
                                    if (currNeighbourLabel == 0)
                                    {
                                        voronoiDiagram[x, y] = currLabel;
                                        pending.Add(currNeighbour);
                                    }
                                    else if (currNeighbourLabel != currLabel)
                                    {
                                        voronoiDiagram[x, y] = -1;
                                    }
                                }
                            }
                        }
                    }
                }
                        
                /*if (pending.Count == 0)
                {
                        Thread.Sleep(1000);
                }*/
            }//main loop end
        }

        private void findSolutions()
        {
            findPath();
        }

        private void findPath()
        {
            combineLabels();
            labelSolutionEnd();
            labelSolutionStart();
            traverseVoronoi();
        }

        private void combineLabels()
        {
            for (int x = 0; x < this.voronoiDiagram.GetLength(0); x++)
            {
                for (int y = 0; y < this.voronoiDiagram.GetLength(1); y++)
                {
                    int currLabel = this.voronoiDiagram[x, y];

                    if (currLabel > 0)
                    {
                        this.voronoiDiagram[x, y] = 8;
                    }
                    else
                    {
                        this.voronoiDiagram[x, y] = 0;
                    }
                }
            }
        }

        private void labelSolutionEnd()
        {
            EndPlanet endPlanet = (EndPlanet)galaxyMap.galaxyComponentsTable["EndPlanet"];
            Vector2 centerPoint = endPlanet.ComponentBounds.centerPOS;
            double radius = endPlanet.ComponentBounds.radius;

            for (int x = (int)Round(centerPoint.X - radius); x <= (int)Round(centerPoint.X + radius); x++)
            {
                for (int y = (int)Round(centerPoint.Y - radius); y <= (int)Round(centerPoint.Y + radius); y++)
                {
                    if (x >= 0 && x < this.voronoiDiagram.GetLength(0) && y >= 0 && y < this.voronoiDiagram.GetLength(1))
                    {
                        double distance = Vector2.Distance(new Vector2(x, y), centerPoint);

                        if (Round(distance) == Round(radius) || distance < radius)
                        {
                            this.voronoiDiagram[x, y] = 3;
                            if (Round(distance) == Round(radius))
                            {
                                Boolean hasAccessPoint = false;

                                for (int xPos = (x - 1); xPos <= (x + 1); xPos++)
                                {
                                    for (int yPos = (y - 1); yPos <= (y + 1); yPos++)
                                    {
                                        if (xPos >= 0 && xPos < this.voronoiDiagram.GetLength(0)
                                            && yPos >= 0 && yPos < this.voronoiDiagram.GetLength(1))
                                        {
                                            int neighbourLabel = this.voronoiDiagram[xPos, yPos];

                                            if (neighbourLabel == 0)
                                            {
                                                hasAccessPoint = true;
                                            }
                                        }
                                    }
                                }

                                if (hasAccessPoint)
                                {
                                    this.solutionQueue.Add(new Vector2(x, y));
                                }
                            }
                        }
                    }
                }
            }
        }

        private void labelSolutionStart()
        {
            StartPlanet startPlanet = (StartPlanet)galaxyMap.galaxyComponentsTable["StartPlanet"];
            Vector2 centerPoint = startPlanet.ComponentBounds.centerPOS;
            double radius = startPlanet.ComponentBounds.radius;

            for (int x = (int)Round(centerPoint.X - radius); x <= (int)Round(centerPoint.X + radius); x++)
            {
                for (int y = (int)Round(centerPoint.Y - radius); y <= (int)Round(centerPoint.Y + radius); y++)
                {
                    if (x >= 0 && x < this.voronoiDiagram.GetLength(0) && y >= 0 && y < this.voronoiDiagram.GetLength(1))
                    {
                        double distance = Vector2.Distance(new Vector2(x, y), centerPoint);

                        if (Round(distance) == Round(radius) || distance < radius)
                        {
                            this.voronoiDiagram[x, y] = 2;
                            if (Round(distance) == Round(radius))
                            {
                                Boolean hasAccessPoint = false;

                                for (int xPos = (x - 1); xPos <= (x + 1); xPos++)
                                {
                                    for (int yPos = (y - 1); yPos <= (y + 1); yPos++)
                                    {
                                        if (xPos >= 0 && xPos < this.voronoiDiagram.GetLength(0)
                                            && yPos >= 0 && yPos < this.voronoiDiagram.GetLength(1))
                                        {
                                            int neighbourLabel = this.voronoiDiagram[xPos, yPos];

                                            if (neighbourLabel == 0)
                                            {
                                                hasAccessPoint = true;
                                            }
                                        }
                                    }
                                }

                                if (hasAccessPoint)
                                {
                                    this.solutionQueue.Add(new Vector2(x, y));
                                }
                            }
                        }
                    }
                }
            }
        }

        private void traverseVoronoi()
        {
            while (solutionQueue.Count > 0)
            {
                Vector2 currMarker = solutionQueue.First();
                solutionQueue.RemoveAt(0);

                int currLabel = this.voronoiDiagram[(int)currMarker.X, (int)currMarker.Y];
                if (currLabel == 2 || currLabel == 3)
                {
                    for (int x = (int)(currMarker.X - 1); x <= (currMarker.X + 1); x++)
                    {
                        for (int y = (int)(currMarker.Y - 1); y <= (currMarker.Y + 1); y++)
                        {
                            if (x >= 0 && x < this.voronoiDiagram.GetLength(0) && y >= 0
                                && y < this.voronoiDiagram.GetLength(1))
                            {
                                int neighbourLabel = this.voronoiDiagram[x, y];

                                if (neighbourLabel == 0)
                                {
                                    this.voronoiDiagram[x, y] = currLabel;
                                    solutionQueue.Add(new Vector2(x, y));
                                }
                                else
                                {
                                    if (currLabel == 2)
                                    {
                                        if (neighbourLabel == 3)
                                        {
                                            //solution found
                                            this.voronoiDiagram[x, y] = currLabel;
                                            mapIsValid = true;
                                            solutionQueue.Clear();
                                            break;
                                        }
                                    }
                                    else if (currLabel == 3)
                                    {
                                        if (neighbourLabel == 2)
                                        {
                                            //solution found
                                            this.voronoiDiagram[x, y] = currLabel;
                                            mapIsValid = true;
                                            solutionQueue.Clear();
                                            break;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        private double Round(double value)
        {
            double difference = Math.Abs(value - Math.Floor(value));
            //Round off or up
            if (difference >= 0.5d)
                return Math.Ceiling(value);
            else
                return Math.Floor(value);
        }
    }
}
